import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.concurrent.ThreadFactory;

import junit.framework.TestCase;

import org.ourgrid.threadServices.MonitoredThreadFactory;
import org.ourgrid.threadServices.ThreadServices;
import org.ourgrid.threadServices.ThreadServicesFactory;



public class TimeoutRouterTest extends TestCase {
	
	private static final byte X = 0;
	private static final byte Y = 1;
	private static final byte Z = 2;
	
	private final int basePort = 2000;
	private ThreadFactory threadFactory;
	
	private Router routerX;
	private Router routerY;
	private Router routerZ;
	private Thread threadRouterX;
	private Thread threadRouterY;
	private Thread threadRouterZ;
	
	private InetSocketAddress socketAddressX;
	private InetSocketAddress socketAddressY;
	private InetSocketAddress socketAddressZ;
	
	private InetSocketAddress[] neighboursX;
	private InetSocketAddress[] neighboursY;
	private InetSocketAddress[] neighboursZ;

	private int[] distanceVectorX;
	private int[] distanceVectorY;
	private int[] distanceVectorZ;
	

	public static void main(String[] args) {
		junit.swingui.TestRunner.run(TimeoutRouterTest.class);
	}

	public TimeoutRouterTest(String name) {
		super(name);
	}

	protected void setUp() throws Exception {

		this.threadFactory = new MonitoredThreadFactory( this.getName() );
		
		this.routerX = new Router( (byte)0, basePort + 0, "127.0.0.1" );
		this.routerY = new Router( (byte)1, basePort + 1, "127.0.0.1" );
		this.routerZ = new Router( (byte)2, basePort + 2, "127.0.0.1" );
		this.threadRouterX = this.threadFactory.newThread( this.routerX );
		this.threadRouterY = this.threadFactory.newThread( this.routerY );
		this.threadRouterZ = this.threadFactory.newThread( this.routerZ );
		
		this.socketAddressX = new InetSocketAddress( "127.0.0.1", basePort+0 );
		this.socketAddressY = new InetSocketAddress( "127.0.0.1", basePort+1 );
		this.socketAddressZ = new InetSocketAddress( "127.0.0.1", basePort+2 );
		
		this.neighboursX = new InetSocketAddress[]{ Router.LOCALHOST, this.socketAddressY, this.socketAddressZ};
		this.distanceVectorX = new int[] { Router.LOCALHOST_COST, 2, 7};

		this.neighboursY = new InetSocketAddress[]{ this.socketAddressX, Router.LOCALHOST, this.socketAddressZ};
		this.distanceVectorY = new int[] { 2, Router.LOCALHOST_COST, 1};
		
		this.neighboursZ = new InetSocketAddress[]{ this.socketAddressX, this.socketAddressY, Router.LOCALHOST};
		this.distanceVectorZ = new int[] { 7, 1, Router.LOCALHOST_COST };
		
		
		this.routerX.setInitialEnlaceState( this.distanceVectorX, this.neighboursX );
		this.routerY.setInitialEnlaceState( this.distanceVectorY, this.neighboursY );
		this.routerZ.setInitialEnlaceState( this.distanceVectorZ, this.neighboursZ );
		
		threadRouterX = this.threadFactory.newThread(routerX);
		threadRouterY = this.threadFactory.newThread(routerY);
		threadRouterZ = this.threadFactory.newThread(routerZ);
		
		super.setUp();
	}

	protected void tearDown() throws Exception {
		super.tearDown();
		this.routerX.shutdown();
		this.routerY.shutdown();
		this.routerZ.shutdown();
		Thread.sleep(100); // wait OS sockets to be closed.
	}
	
	
	public void testUnderControlledEnviroment() throws InterruptedException{

//		Verify distances
		assertEquals( 0, this.routerX.getDistanceVector()[X] );
		assertEquals( 2, this.routerX.getDistanceVector()[Y] );
		assertEquals( 7, this.routerX.getDistanceVector()[Z] );
		assertEquals( 2, this.routerY.getDistanceVector()[X] );
		assertEquals( 0, this.routerY.getDistanceVector()[Y] );
		assertEquals( 1, this.routerY.getDistanceVector()[Z] );
		assertEquals( 7, this.routerZ.getDistanceVector()[X] );
		assertEquals( 1, this.routerZ.getDistanceVector()[Y] );
		assertEquals( 0, this.routerZ.getDistanceVector()[Z] );
		
//		Verify predecesors
		assertEquals( X, this.routerX.getPIVector()[X] );
		assertEquals( Y, this.routerX.getPIVector()[Y] );
		assertEquals( Z, this.routerX.getPIVector()[Z] );
		assertEquals( X, this.routerY.getPIVector()[X] );
		assertEquals( Y, this.routerY.getPIVector()[Y] );
		assertEquals( Z, this.routerY.getPIVector()[Z] );
		assertEquals( X, this.routerZ.getPIVector()[X] );
		assertEquals( Y, this.routerZ.getPIVector()[Y] );
		assertEquals( Z, this.routerZ.getPIVector()[Z] );
		
		this.threadRouterX.start();
		this.threadRouterY.start();
		this.threadRouterZ.start();
		Thread.sleep(1000);
		
//		Verify distances
		assertEquals( 0, this.routerX.getDistanceVector()[X] );
		assertEquals( 2, this.routerX.getDistanceVector()[Y] );
		assertEquals( 3, this.routerX.getDistanceVector()[Z] );
		assertEquals( 2, this.routerY.getDistanceVector()[X] );
		assertEquals( 0, this.routerY.getDistanceVector()[Y] );
		assertEquals( 1, this.routerY.getDistanceVector()[Z] );
		assertEquals( 3, this.routerZ.getDistanceVector()[X] );
		assertEquals( 1, this.routerZ.getDistanceVector()[Y] );
		assertEquals( 0, this.routerZ.getDistanceVector()[Z] );
		
//		Verify predecesors
		assertEquals( X, this.routerX.getPIVector()[X] );
		assertEquals( Y, this.routerX.getPIVector()[Y] );
		assertEquals( Y, this.routerX.getPIVector()[Z] );
		assertEquals( X, this.routerY.getPIVector()[X] );
		assertEquals( Y, this.routerY.getPIVector()[Y] );
		assertEquals( Z, this.routerY.getPIVector()[Z] );
		assertEquals( Y, this.routerZ.getPIVector()[X] );
		assertEquals( Y, this.routerZ.getPIVector()[Y] );
		assertEquals( Z, this.routerZ.getPIVector()[Z] );
		
		System.out.println( this.routerX );
		System.out.println( this.routerZ );
		this.routerY.shutdown();
		Thread.sleep( (long) (1.5f * Router.TIMEOUT) );
		
		
//		Verify distances
		assertEquals( 0, this.routerX.getDistanceVector()[X] );
		assertEquals( Router.INFINITY, this.routerX.getDistanceVector()[Y] );
		assertEquals( 7, this.routerX.getDistanceVector()[Z] );
		assertEquals( 7, this.routerZ.getDistanceVector()[X] );
		assertEquals( Router.INFINITY, this.routerZ.getDistanceVector()[Y] );
		assertEquals( 0, this.routerZ.getDistanceVector()[Z] );
		
//		Verify predecesors
		assertEquals( X, this.routerX.getPIVector()[X] );
		assertEquals( Router.NO_ROUTE_TO_HOST, this.routerX.getPIVector()[Y] );
		assertEquals( Z, this.routerX.getPIVector()[Z] );
		assertEquals( X, this.routerZ.getPIVector()[0] );
		assertEquals( Router.NO_ROUTE_TO_HOST, this.routerZ.getPIVector()[Y] );
		assertEquals( Z, this.routerZ.getPIVector()[Z] );		

		this.threadRouterY = this.threadFactory.newThread( routerY );
		this.threadRouterY.start();
		Thread.sleep(3000);
		
		
//		Verify distances
		assertEquals( 0, this.routerX.getDistanceVector()[X] );
		assertEquals( 2, this.routerX.getDistanceVector()[Y] );
		assertEquals( 3, this.routerX.getDistanceVector()[Z] );
		assertEquals( 2, this.routerY.getDistanceVector()[X] );
		assertEquals( 0, this.routerY.getDistanceVector()[Y] );
		assertEquals( 1, this.routerY.getDistanceVector()[Z] );
		assertEquals( 3, this.routerZ.getDistanceVector()[X] );
		assertEquals( 1, this.routerZ.getDistanceVector()[Y] );
		assertEquals( 0, this.routerZ.getDistanceVector()[Z] );
		
//		Verify predecesors
		assertEquals( X, this.routerX.getPIVector()[X] );
		assertEquals( Y, this.routerX.getPIVector()[Y] );
		assertEquals( Y, this.routerX.getPIVector()[Z] );
		assertEquals( X, this.routerY.getPIVector()[X] );
		assertEquals( Y, this.routerY.getPIVector()[Y] );
		assertEquals( Z, this.routerY.getPIVector()[Z] );
		assertEquals( Y, this.routerZ.getPIVector()[X] );
		assertEquals( Y, this.routerZ.getPIVector()[Y] );
		assertEquals( Z, this.routerZ.getPIVector()[Z] );
		


	}


}
